package com.hentie.drawlib;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import com.hentie.mathlib.Vector2;
import com.hentie.mathlib.Vector3;
import com.hentie.spritedrawlib.Sprite;
import com.hentie.utils.StopWatch;

import java.io.IOException;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.List;

import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.util.Log;





public class OpenGLRenderer implements Renderer {

	public boolean isReady = false;
	//Plane vertex buffer
	public FloatBuffer vertexBuffer;
	public FloatBuffer textureBuffer;
	public ShortBuffer indexBuffer;
	public static final int MAX_QUEUE=400;
	Queue<DrawableObject>drawQueue = new LinkedList<DrawableObject>();
	//Our sorted textures
	Map<Integer,List<DrawableObject>>textureSort = new Hashtable<Integer,List<DrawableObject>>();
	//The texture resource
	Map<String,Integer>textureResource = new Hashtable<String,Integer>();
	
	//DrawList[] batchedDraws;
	
	
	
	//Important functions needed
	GL10 glContext;
	Context appCtx;
	AssetManager assetsManager;

	
	//StopWatch for track time
	StopWatch dataConstruct = new StopWatch();
	
	//A test object
	DrawableObject testObject =new DrawableObject();

	private Object mDrawLock = new Object();
	private Object mInsertLock = new Object();
	boolean drawQueueUpdate = false;
	Sprite testSprite;
	
	Vector3 cameraPosition;
	float cameraAngle;
	
	//Our constructor
	public OpenGLRenderer(Context context)
	{
		assetsManager = context.getAssets();
		//Setup the test object

		this.appCtx = context;
		AllocatePlaneBuffers();
	}
	
	
	public int getTextureResource(String textureName)
	{
		//Try to find it in the texture resource
		if(textureResource.containsKey(textureName)==true)
			return textureResource.get(textureName);
		else if(textureResource.containsKey(textureName)==false)
			return LoadTexture(textureName);
		else
			return -1;
	}
	
	public void AllocatePlaneBuffers()
	{
		vertexBuffer = ByteBuffer.allocateDirect(12*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
		vertexBuffer.rewind();
		indexBuffer = ByteBuffer.allocateDirect(6*2).order(ByteOrder.nativeOrder()).asShortBuffer();
		indexBuffer.rewind();
		textureBuffer = ByteBuffer.allocateDirect(8*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
		textureBuffer.rewind();

		
	}
	
	private int LoadTexture(String texture)
	{
		
		BitmapFactory.Options opts = new BitmapFactory.Options();
		opts.inScaled = false;
		Bitmap textureBitmap=null;
		try {
			textureBitmap = BitmapFactory.decodeStream(assetsManager.open(texture),null, opts);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		

		
		//generate a new texture id
		int[] textures = new int[1];
		glContext.glGenTextures(1, textures,0);
	    // ...and bind it to our array
		glContext.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
		int texID = textures[0];
		textureResource.put(texture, texID);
		glContext.glTexParameterf(GL10.GL_TEXTURE_2D,
                GL10.GL_TEXTURE_MAG_FILTER,
                GL10.GL_NEAREST);
		glContext.glTexParameterf(GL10.GL_TEXTURE_2D,
                GL10.GL_TEXTURE_MIN_FILTER,
                GL10.GL_NEAREST);
		
		
		
		//Set it
		GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, textureBitmap, 0);
		return texID;
		
	}
	
	private final String TAG = "OpenGLRenderer";
	
	float rot;
	public void onDrawFrame(GL10 gl) {


        DrawSingles(gl);
	 // FlushQueueAndSort();
	  
	 // textureSort.clear();

	}
		
	

	private void DrawFromBufferObject(DrawBuffer drawBuffer)
	{
		
	}
	
	private void DrawSingleObject(DrawableObject draw,GL10 gl)
	{

		


	
			
		//Draw it
		gl.glFrontFace(GL10.GL_CCW);
		gl.glEnable(GL10.GL_CULL_FACE);
		gl.glCullFace(GL10.GL_BACK);

		//Put them in the vertex buffer
		vertexBuffer.rewind();
		vertexBuffer.put(draw.vertexPoints);
		vertexBuffer.rewind();
		//Put them in the vertex buffer
		textureBuffer.rewind();
		textureBuffer.put(draw.textureCoords);
		textureBuffer.rewind();
		//Put them in the vertex buffer
		indexBuffer.rewind();
		indexBuffer.put(draw.indicies);
		indexBuffer.rewind();
		
		
		if(draw.useTextures && draw.glTexture == -1)
		{
			//try to get the texture
			draw.glTexture = getTextureResource(draw.textureName);
		}
		if(draw.glTexture != -1)
		{
		// Telling OpenGL to enable textures.
	    gl.glEnable(GL10.GL_TEXTURE_2D);
		 //Tell OpenGL where our texture is located.
		gl.glBindTexture(GL10.GL_TEXTURE_2D, draw.glTexture);
		 //Tell OpenGL to enable the use of UV coordinates.
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		// Telling OpenGL where our UV coordinates are.
		gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0,textureBuffer);	
		}
		
		gl.glPushMatrix();
		

		gl.glTranslatef(draw.position.X(), draw.position.Y(), draw.position.Z());
		gl.glRotatef(draw.rotation, 0, 0, 1.0f);
		gl.glScalef(draw.scale.X(), draw.scale.Y(), draw.scale.Z());
		
		
		
		
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, // OpenGL docs
				vertexBuffer);


		gl.glDrawElements(GL10.GL_TRIANGLES, draw.indicies.length,// OpenGL docs
				  GL10.GL_UNSIGNED_SHORT, indexBuffer);
		
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); // OpenGL docs
		gl.glDisable(GL10.GL_CULL_FACE); // OpenGL docs
		gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
		// Disable the use of UV coordinates.
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		// Disable the use of textures.
		gl.glDisable(GL10.GL_TEXTURE_2D);

		gl.glPopMatrix();

		
		


	}
	
	
	public void AddToRenderQueue(DrawableObject toDraw)
	{
		drawQueue.add(toDraw);
        synchronized(mDrawLock) {
           drawQueueUpdate = true;
            mDrawLock.notify();
    }
	}
	
	public synchronized void SetRenderQueue(Queue<DrawableObject> drawable)
	{

		if(drawQueue.isEmpty())
		drawQueue.addAll(drawable);
		Log.v(TAG,"Got queue of "+ String.valueOf(drawQueue.size()));
        synchronized(mDrawLock) {
            drawQueueUpdate = true;
             mDrawLock.notify();
        }
     

	}
	
	
	StopWatch checkConstruct = new StopWatch();
	
	public static <T> T[] arrayMerge(T[]... arrays)
	{
	    // Determine required size of new array

	    int count = 0;
	    for (T[] array : arrays)
	    {
	        count += array.length;
	    }

	    // create new array of required class

	    T[] mergedArray = (T[]) Array.newInstance(
	       arrays[0][0].getClass(),count);

	    // Merge each array into new array

	    int start = 0;
	    for (T[] array : arrays)
	    {
	        System.arraycopy(array, 0,
	           mergedArray, start, array.length);
	        start += array.length;
	    }
	    return (T[]) mergedArray;
	} 
	public static  float[] floatArrayMerge(float[]... arrays)
	{
	    // Determine required size of new array

	    int count = 0;
	    for (int i = 0; i < arrays.length; i++)
	    {
	        count += arrays[i].length;
	    }

	    // create new array of required class

	    float[] mergedArray = new float[count];

	    // Merge each array into new array

	    int start = 0;
	    for (int i = 0; i < arrays.length; i++)
	    {
	        System.arraycopy(arrays[i], 0,
	           mergedArray, start, arrays[i].length);
	        start += arrays[i].length;
	    }
	    return mergedArray;
	} 

	public static  short[] shortArrayMerge(short[]... arrays)
	{
	    // Determine required size of new array

	    int count = 0;
	    for (int i = 0; i < arrays.length; i++)
	    {
	        count += arrays[i].length;
	    }

	    // create new array of required class

	    short[] mergedArray = new short[count];

	    // Merge each array into new array

	    int start = 0;
	    for (int i = 0; i < arrays.length; i++)
	    {
	        System.arraycopy(arrays[i], 0,
	           mergedArray, start, arrays[i].length);
	        start += arrays[i].length;
	    }
	    return mergedArray;
	}
	/*
	//Only runs if the queue Updates
	private void BatchDraw(GL10 gl)
	{


        synchronized(mDrawLock) {
            if (!drawQueueUpdate) {
                while (!drawQueueUpdate) {
                    try {
                        mDrawLock.wait();
                    } catch (InterruptedException e) {
                        // No big deal if this wait is interrupted.
                    }
                }
            }
            drawQueueUpdate = false;
        }
        
		FlushQueueAndSort();
		checkConstruct.start();
	//	ConstructDrawData();
		checkConstruct.stop();
		Log.v("OPENGL", "Batch Time: "+String.valueOf(checkConstruct.getElapsedTimeMilli())+" ms");
		checkConstruct.reset();
		//Now we have the stuf lets draw it
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | // OpenGL docs.
                GL10.GL_DEPTH_BUFFER_BIT);

	
		int drawCalls=0;
			
		
		synchronized(this)
		{
		
		//Set Culling
		gl.glFrontFace(GL10.GL_CCW);
		gl.glEnable(GL10.GL_CULL_FACE);
		gl.glCullFace(GL10.GL_BACK);		
		for(int i = 0; i < batchedDraws.length; i++)
		{
	
			drawCalls++;
			if(batchedDraws[i].textureId != -1)
			{
				// Telling OpenGL to enable textures.
			    gl.glEnable(GL10.GL_TEXTURE_2D);
				 //Tell OpenGL where our texture is located.
				gl.glBindTexture(GL10.GL_TEXTURE_2D, batchedDraws[i].textureId);
				 //Tell OpenGL to enable the use of UV coordinates.
				gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
				// Telling OpenGL where our UV coordinates are.
				gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, batchedDraws[i].textureBuffer);	
			}
			
			//Point to vertex buffer
			gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
			gl.glVertexPointer(3, GL10.GL_FLOAT, 0, // OpenGL docs
					batchedDraws[i].vertexBuffer);
			//Draw elements
			gl.glDrawElements(GL10.GL_TRIANGLES, batchedDraws[i].indices.length,// OpenGL docs
					  GL10.GL_UNSIGNED_SHORT, batchedDraws[i].indexBuffer);
			
			//Clear everything for next batch
			gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); // OpenGL docs
			gl.glDisable(GL10.GL_CULL_FACE); // OpenGL docs
			gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
			// Disable the use of UV coordinates.
			gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
			// Disable the use of textures.
			gl.glDisable(GL10.GL_TEXTURE_2D);
			
			//Log.v("Draw Calls: ", String.valueOf(drawCalls));
			
		
		}
		}
	
		textureSort.clear();
		drawQueue.clear();
		//Queue is reset
		drawQueueUpdate = false;
		
		
		
	}
	*/
	private float[] buildFloatArray(List<Float> floats)
	{
		float[] floatArr = new float[floats.size()];
		int i = 0;
		for(Float n : floats)
		{
			floatArr[i]=n;
			i++;
		}
		return floatArr;
	}
	private short[] buildShortArray(List<Short> floats)
	{
		short[] floatArr = new short[floats.size()];
		int i = 0;
		for(Short n : floats)
		{
			floatArr[i]=n;
			i++;
		}
		return floatArr;
	}	
	
	//private void DrawUntextured
	
	public void onSurfaceChanged(GL10 gl, int width, int height) {

		// Sets the current v iew port to the new size.
		gl.glViewport(0, 0, width, height);// OpenGL docs.
		// Select the projection matrix
		gl.glMatrixMode(GL10.GL_PROJECTION);// OpenGL docs.
		// Reset the projection matrix
		gl.glLoadIdentity();// OpenGL docs.
		// Calculate the aspect ratio of the window
		GLU.gluPerspective(gl, 45.0f,
                                   (float) width / (float) height,
                                   0.1f, 1000.0f);
		// Select the modelview matrix
		gl.glMatrixMode(GL10.GL_MODELVIEW);// OpenGL docs.
		// Reset the modelview matrix
		gl.glLoadIdentity();// OpenGL docs.

	}

    public synchronized void waitDrawingComplete() {
    }

	
	public void onSurfaceCreated(GL10 gl, EGLConfig arg1) {
		// TODO Auto-generated method stub
		// TODO Auto-generated method stub
		gl.glClearColor(0, 0, 0.5f, 1.0f);
		gl.glShadeModel(GL10.GL_SMOOTH);
		gl.glClearDepthf(1.0f);
		gl.glEnable(GL10.GL_DEPTH_TEST);
		gl.glDepthFunc(GL10.GL_LEQUAL);
		gl.glBlendFunc(GL10.GL_SRC_ALPHA,GL10.GL_ONE_MINUS_SRC_ALPHA);
		gl.glEnable(GL10.GL_BLEND);
		gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, // OpenGL docs.
                GL10.GL_NICEST);
		glContext = gl;

		testObject.glTexture = getTextureResource("Sprites/AYYYY.png");
		testSprite = new Sprite(appCtx,"Sprites/Goku/Goku.xml");
		isReady = true;
		
	}

	public void DrawSingles(GL10 gl)
	{

		 Log.v(TAG, "Our queue is 1: "+String.valueOf(drawQueue.size()));
		// TODO Auto-generated method stub
       synchronized(mDrawLock) {
           if (!drawQueueUpdate) {
               while (!drawQueueUpdate) {
                   try {
                       mDrawLock.wait();
                   } catch (InterruptedException e) {
                     
                   }
               }
           }
           drawQueueUpdate = false;
       }

        
        
  
        cameraAngle = Camera.GetAngle();
        cameraPosition = Camera.GetPosition();
        
        if(!drawQueue.isEmpty())
        {
        //Clear the screen
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | // OpenGL docs.
                GL10.GL_DEPTH_BUFFER_BIT);
        }
        //Set up the camera
        gl.glRotatef(cameraAngle, 0,0,1.0f);
        gl.glTranslatef(cameraPosition.X(), cameraPosition.Y(), cameraPosition.Z());
        gl.glPushMatrix();
        

        Log.v("OPENGL", "Objects in queue: "+String.valueOf(drawQueue.size()));
        
        
        synchronized(this)
        {    
		while(drawQueue.isEmpty() ==false)
		{
			DrawableObject toDraw=null;
		
			Log.v("OpengGL", "Left:" +String.valueOf(drawQueue.size()));
			
		
			//Try to remove from queue
			try{
				toDraw = drawQueue.poll();
			}catch(NoSuchElementException exception){
				exception.printStackTrace();
				break;
			}
				
			if(toDraw != null)
			  DrawSingleObject(toDraw,gl);
			else
				break;
		}
        }
		gl.glPopMatrix();
      
		gl.glLoadIdentity();
		

		drawQueue.clear();
        }
        

	public void BatchDraw(GL10 gl)
	{
		
		
	}
	
}
